﻿/************************************************************************

   Extended Silverlight Toolkit

   Copyright (C) 2010-2012 Xceed Software Inc.

   This program is provided to you under the terms of the Microsoft Public
   License (Ms-PL) as published at http://extendedsilverlight.codeplex.com/license

   Please purchase a commercial version of this toolkit if you are using
   it in a commercial product. The commercial versions support the project,
   and also include more features for each control, priority support,
   source code, updates, removed watermark, and a proprietary license.

   Visit http://xceed.com and follow @datagrid on Twitter.

  **********************************************************************/

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using System.Windows.Data;

namespace Xceed.Silverlight.Data
{
  internal sealed class GroupLevelCollection : IList<IGroupCollection>, ICollection<IGroupCollection>, ICollection, IEnumerable<IGroupCollection>, IEnumerable, INotifyCollectionChanged
  {
    #region Constructors

    internal GroupLevelCollection( ObservableCollection<GroupDescription> groupDescriptions )
    {
      m_groupDescriptions = groupDescriptions;
      m_groupDescriptions.CollectionChanged += new NotifyCollectionChangedEventHandler( this.OnGroupDescriptionsCollectionChanged );

      IEnumerable<IGroupCollection> groupCollections = ( from gd in groupDescriptions
                                                         select new GroupCollection() ).Cast<IGroupCollection>();
      this.ResetGroupCollections( groupCollections );
    }

    internal GroupLevelCollection( IEnumerable<IGroupCollection> groupCollections )
    {
      this.ResetGroupCollections( groupCollections );
    }

    #endregion

    #region ExpansionChanging Event

    internal event EventHandler<ExpansionChangedEventArgs> ExpansionChanging;

    private void OnExpansionChanging( ExpansionChangedEventArgs e )
    {
      if( e == null )
        return;

      EventHandler<ExpansionChangedEventArgs> handlers = this.ExpansionChanging;

      if( handlers != null )
      {
        handlers.Invoke( this, e );
      }
    }

    #endregion

    #region ExpansionChanged Event

    internal event EventHandler<ExpansionChangedEventArgs> ExpansionChanged;

    private void OnExpansionChanged( ExpansionChangedEventArgs e )
    {
      if( e == null )
        return;

      EventHandler<ExpansionChangedEventArgs> handlers = this.ExpansionChanged;

      if( handlers != null )
      {
        handlers.Invoke( this, e );
      }
    }

    #endregion

    internal bool IsCollapsed( DataPath dataPath )
    {
      if( dataPath == null )
        throw new ArgumentNullException( "dataPath" );

      lock( m_syncRoot )
      {
        int depth = Math.Min( dataPath.Depth, m_groupCollections.Count );

        for( int i = 1; i <= depth; i++ )
        {
          if( this.IsCollapsedAtTargetLevel( dataPath, i ) )
            return true;
        }
      }

      return false;
    }

    internal bool IsCollapsedAtTargetLevel( DataPath dataPath, int level )
    {
      if( dataPath == null )
        throw new ArgumentNullException( "dataPath" );

      if( ( level <= 0 ) || ( level > dataPath.Depth ) )
        throw new ArgumentException( "The level must be greater than or equal to one and lesser than or equal to dataPath.Depth.", "level" );

      lock( m_syncRoot )
      {
        if( level > m_groupCollections.Count )
          throw new ArgumentException( "The specified level is higher than the number of group level.", "level" );

        DataPath groupPath = ( dataPath.Depth == level )
                               ? dataPath
                               : dataPath.CreateAncestorOrSamePath( level );

        if( !( groupPath.LastChild is GroupDataItem ) )
          throw new ArgumentException( "The specified path must refer to a GroupDataItem instance.", "dataPath" );

        IGroupCollection collection = m_groupCollections[ level - 1 ];

        return ( collection.IsCollapsedByDefault != collection.Contains( groupPath ) );
      }
    }

    internal int GetCollapsedDepth( DataPath dataPath )
    {
      if( dataPath == null )
        throw new ArgumentNullException( "dataPath" );

      lock( m_syncRoot )
      {
        int depth = Math.Min( dataPath.Depth, m_groupCollections.Count );

        for( int i = 0; i < depth; i++ )
        {
          if( !( dataPath[ i ] is GroupDataItem ) )
            break;

          int currentDepth = i + 1;

          if( this.IsCollapsedAtTargetLevel( dataPath, currentDepth ) )
            return currentDepth;
        }
      }

      // A value of zero means the item isn't collapsed.
      return 0;
    }

    internal IEnumerator<GroupExpansionState> GetGroupExpansionStateEnumerator()
    {
      return new GroupExpansionStateEnumerator( m_groupCollections );
    }

    private static void ValidateGroupDescription( GroupDescription groupDescription )
    {
      if( groupDescription is PropertyGroupDescription )
      {
        if( ( ( PropertyGroupDescription )groupDescription ).Converter != null )
          throw new InvalidOperationException( "IValueConverters are not supported when using a virtualized data source." );
      }
      else if( groupDescription is DataGroupDescription )
      {
        if( ( ( DataGroupDescription )groupDescription ).SortComparer != null )
          throw new InvalidOperationException( "SortComparers are not supported when using a virtualized data source." );
      }
      else
      {
        throw new InvalidOperationException( "Only PropertyGroupDescription and DataGroupDescription instances are supported." );
      }
    }

    private void ResetGroupCollections( IEnumerable<IGroupCollection> groupCollections )
    {
      foreach( IGroupCollection collection in m_groupCollections )
      {
        collection.CollectionChanging -= new EventHandler<GroupCollectionChangedEventArgs>( this.OnGroupCollectionChanging );
        collection.CollectionChanged -= new EventHandler<GroupCollectionChangedEventArgs>( this.OnGroupCollectionChanged );
      }

      m_groupCollections.Clear();

      foreach( IGroupCollection collection in groupCollections )
      {
        collection.CollectionChanging += new EventHandler<GroupCollectionChangedEventArgs>( this.OnGroupCollectionChanging );
        collection.CollectionChanged += new EventHandler<GroupCollectionChangedEventArgs>( this.OnGroupCollectionChanged );

        m_groupCollections.Add( collection );
      }
    }

    private ExpansionChangedEventArgs CreateExpansionChangedEventArgs( IGroupCollection sender, GroupCollectionChangedEventArgs e )
    {
      if( sender == null )
        throw new ArgumentNullException( "sender" );

      if( e == null )
        throw new ArgumentNullException( "e" );

      int depth;

      lock( m_syncRoot )
      {
        depth = m_groupCollections.IndexOf( sender ) + 1;
      }

      if( depth <= 0 )
        throw new DataInternalException();

      DataPath target = e.Target as DataPath;

      switch( e.Action )
      {
        case GroupCollectionChangedAction.Collapse:
          return new ExpansionChangedEventArgs( ExpansionAction.Collapse, target );

        case GroupCollectionChangedAction.CollapseAll:
          return new ExpansionChangedEventArgs( ExpansionAction.CollapseAll, depth );

        case GroupCollectionChangedAction.Expand:
          return new ExpansionChangedEventArgs( ExpansionAction.Expand, target );

        case GroupCollectionChangedAction.ExpandAll:
          return new ExpansionChangedEventArgs( ExpansionAction.ExpandAll, depth );

        default:
          throw new DataInternalException();
      }
    }

    private void OnGroupDescriptionsCollectionChanged( object sender, NotifyCollectionChangedEventArgs e )
    {
      lock( m_syncRoot )
      {
        IEnumerable<GroupDescription> groupDescriptions = sender as IEnumerable<GroupDescription>;
        if( groupDescriptions == null )
          return;

        foreach( GroupDescription groupDescription in groupDescriptions )
        {
          GroupLevelCollection.ValidateGroupDescription( groupDescription );
        }

        IEnumerable<IGroupCollection> groupCollections = ( from gd in groupDescriptions
                                                           select new GroupCollection() ).Cast<IGroupCollection>();

        this.ResetGroupCollections( groupCollections );
      }

      this.OnCollectionChanged();
    }

    private void OnGroupCollectionChanging( object sender, GroupCollectionChangedEventArgs e )
    {
      this.OnExpansionChanging(
        this.CreateExpansionChangedEventArgs( sender as IGroupCollection, e ) );
    }

    private void OnGroupCollectionChanged( object sender, GroupCollectionChangedEventArgs e )
    {
      this.OnExpansionChanged(
        this.CreateExpansionChangedEventArgs( sender as IGroupCollection, e ) );
    }

    #region IList<IGroupCollection> Members

    public IGroupCollection this[ int index ]
    {
      get
      {
        lock( m_syncRoot )
        {
          return m_groupCollections[ index ];
        }
      }
      set
      {
        throw new NotSupportedException();
      }
    }

    public int IndexOf( IGroupCollection item )
    {
      lock( m_syncRoot )
      {
        return m_groupCollections.IndexOf( item );
      }
    }

    void IList<IGroupCollection>.Insert( int index, IGroupCollection item )
    {
      throw new NotSupportedException();
    }

    void IList<IGroupCollection>.RemoveAt( int index )
    {
      throw new NotSupportedException();
    }

    #endregion

    #region ICollection<IGroupCollection> Members

    public bool IsReadOnly
    {
      get
      {
        return true;
      }
    }

    public int Count
    {
      get
      {
        lock( m_syncRoot )
        {
          return m_groupCollections.Count;
        }
      }
    }

    public bool Contains( IGroupCollection item )
    {
      lock( m_syncRoot )
      {
        return m_groupCollections.Contains( item );
      }
    }

    public void CopyTo( IGroupCollection[] array, int arrayIndex )
    {
      lock( m_syncRoot )
      {
        m_groupCollections.CopyTo( array, arrayIndex );
      }
    }

    void ICollection<IGroupCollection>.Clear()
    {
      throw new NotSupportedException();
    }

    void ICollection<IGroupCollection>.Add( IGroupCollection item )
    {
      throw new NotSupportedException();
    }

    bool ICollection<IGroupCollection>.Remove( IGroupCollection item )
    {
      throw new NotSupportedException();
    }

    #endregion

    #region ICollection Members

    public object SyncRoot
    {
      get
      {
        return m_syncRoot;
      }
    }

    bool ICollection.IsSynchronized
    {
      get
      {
        return true;
      }
    }

    void ICollection.CopyTo( Array array, int index )
    {
      if( array == null )
        throw new ArgumentNullException( "array" );

      lock( m_syncRoot )
      {
        lock( array.SyncRoot )
        {
          int currentIndex = index;

          foreach( IGroupCollection item in m_groupCollections )
          {
            array.SetValue( item, currentIndex );
            currentIndex++;
          }
        }
      }
    }

    #endregion

    #region IEnumerable<IGroupCollection> Members

    public IEnumerator<IGroupCollection> GetEnumerator()
    {
      return m_groupCollections.GetEnumerator();
    }

    #endregion

    #region IEnumerable Members

    IEnumerator IEnumerable.GetEnumerator()
    {
      IEnumerable<IGroupCollection> source = this;

      return source.GetEnumerator();
    }

    #endregion

    #region INotifyCollectionChanged Members

    public event NotifyCollectionChangedEventHandler CollectionChanged;

    private void OnCollectionChanged()
    {
      NotifyCollectionChangedEventHandler handlers = this.CollectionChanged;

      if( handlers != null )
      {
        NotifyCollectionChangedEventArgs e = new NotifyCollectionChangedEventArgs( NotifyCollectionChangedAction.Reset );

        handlers.Invoke( this, e );
      }
    }

    #endregion

    #region Private Fields

    private readonly ObservableCollection<GroupDescription> m_groupDescriptions;
    private readonly IList<IGroupCollection> m_groupCollections = new List<IGroupCollection>( 0 );
    private readonly object m_syncRoot = new object();

    #endregion

    #region GroupExpansionStateEnumerator Nested Type

    private class GroupExpansionStateEnumerator : IEnumerator<GroupExpansionState>
    {
      internal GroupExpansionStateEnumerator( IEnumerable<IGroupCollection> groupCollections )
      {
        if( groupCollections == null )
          throw new ArgumentNullException( "groupCollections" );

        m_groupCollectionsEnumerator = groupCollections.GetEnumerator();
      }

      public GroupExpansionState Current
      {
        get
        {
          if( m_groupsEnumerator == null )
            throw new InvalidOperationException();

          GroupState state = ( m_groupCollectionsEnumerator.Current.IsCollapsedByDefault )
                               ? GroupState.Expanded
                               : GroupState.Collapsed;

          return new GroupExpansionState( m_groupsEnumerator.Current, state );
        }
      }

      public void Dispose()
      {
        m_groupsEnumerator.Dispose();
        m_groupCollectionsEnumerator.Dispose();
      }

      public bool MoveNext()
      {
        do
        {
          if( m_groupsEnumerator == null )
          {
            if( !m_groupCollectionsEnumerator.MoveNext() )
              break;

            m_groupsEnumerator = m_groupCollectionsEnumerator.Current.GetEnumerator();
          }

          if( m_groupsEnumerator.MoveNext() )
            return true;

          m_groupsEnumerator = null;
        }
        while( m_groupsEnumerator == null );

        return false;
      }

      public void Reset()
      {
        m_groupsEnumerator.Dispose();
        m_groupsEnumerator = null;
        m_groupCollectionsEnumerator.Reset();
      }

      object IEnumerator.Current
      {
        get
        {
          return this.Current;
        }
      }

      private IEnumerator<DataPath> m_groupsEnumerator; //null
      private readonly IEnumerator<IGroupCollection> m_groupCollectionsEnumerator;
    }

    #endregion
  }
}
